package context.render.state;

import context.render.framebuffer.FrameBuffer;
import context.render.shader.Shader;
import java.nio.IntBuffer;
import math.vec2i;
import math.vec3;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;

public class StateGPU
{
    
    public StateGPU()
    {
    }
    
    
    /** Perform an error check on OpenGL method calls and output if found. */
    public void errorCheckGL()
    {
        int err = GL11.glGetError();
        if (err != 0)
        {
            System.err.println("GL ERROR: " + err);
        }
    }
    
    /** Clears the GPU's color, depth, and stencil buffers. This will erase the bound FrameBuffer. */
    public void clearBuffers()
    {
        GL11.glClear(
            GL11.GL_DEPTH_BUFFER_BIT | 
            GL11.GL_COLOR_BUFFER_BIT | 
            GL11.GL_STENCIL_BUFFER_BIT
        );
    }
    
    /** Sets the viewport dimensions for rendering. */
    public void viewport(
            vec2i aStart, 
            vec2i aSize)
    {
        GL11.glViewport(aStart.x, aStart.y, aSize.x, aSize.y);
    }
    
    /** Sets CCW face culling on or off. */
    public void cullFace(boolean aCCW, boolean aDoCull)
    {
        GL11.glFrontFace(aCCW ? GL11.GL_CCW : GL11.GL_CW);
        if (aDoCull)
        {
            // Set Triangle Culling = CCW:
            GL11.glEnable(GL11.GL_CULL_FACE);
            GL11.glCullFace(GL11.GL_BACK);
        }
        else
        {
            // Disable Triangle Culling:
            GL11.glDisable(GL11.GL_CULL_FACE);
        }
    }
    
    /** Sets depth testing on or off. */
    public void depthTest(boolean aDoDepthTest)
    {
        if (aDoDepthTest)
        {
            GL11.glEnable(GL11.GL_DEPTH_TEST);
            GL11.glDepthFunc(GL11.GL_LESS);
        }
        else
        {
            GL11.glDisable(GL11.GL_DEPTH_TEST);
        }
    }
    
    /** Sets additive blending on or off. */
    public void additiveColorBlend(boolean aDoColorBlending)
    {
        if (aDoColorBlending)
        {
            GL11.glEnable(GL11.GL_BLEND);
            GL14.glBlendEquation(GL14.GL_FUNC_ADD);
            GL11.glBlendFunc(GL11.GL_SRC_COLOR, GL11.GL_DST_COLOR);
        }
        else
        {
            GL11.glDisable(GL11.GL_BLEND);
        }
    }
    
    /** Sets the clear color for use when the GPU buffers are cleared. */
    public void clearColor(
            vec3 aColor, 
            double aAlpha)
    {
        GL11.glClearColor(
            (float) aColor.x, 
            (float) aColor.y, 
            (float) aColor.z, 
            (float) aAlpha
        );
    }
    
    /** Binds a FrameBuffer to the GPU. A null argument will unbind all FrameBuffers. */
    public void frameBuffer(FrameBuffer aFrameBuffer)
    {
        if (aFrameBuffer == null)
        {
            GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
        }
        else
        {
            GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, aFrameBuffer.mID);
            IntBuffer drawBuffers = aFrameBuffer.getDrawBuffers();
            GL20.glDrawBuffers(drawBuffers);
        }
    }
    
    /** Binds a Shader program to the GPU. A null argument will unbind all Shaders. */
    public void shader(Shader aShader)
    {
        // Let the GPU change to this shader:
        if (aShader == null)
        {
            GL20.glUseProgram(0);
        }
        else
        {
            GL20.glUseProgram(aShader.mID);
        }
    }
    
}
